//
//  Requestable.swift
//  AG
//
//  Created by BirdMichael on 2020/4/27.
//  Copyright © 2020 BirdMichael. All rights reserved.
//

import Foundation
import HandyJSON
import BWToolKit

public protocol Requestable {
    associatedtype T: HandyJSON
    typealias ResultHandler = Request.ResultingModel<T>
    @discardableResult static func request(_ request: Request, result: ResultHandler?) -> Request
    @discardableResult static func request(api: API, result: ResultHandler?) -> Request
}

extension Requestable {
    @discardableResult
    public static func request(_ api: RequestAPI, result: ResultHandler?) -> Request {
        return Networker.send(request: api.request, callback: handleResponse(result))
    }
    
    @discardableResult
    public static func request(_ request: Request, result: ResultHandler?) -> Request {
        return Networker.send(request: request, callback: handleResponse(result))
    }
    
    @discardableResult
    public static func request(api: API, result: ResultHandler?) -> Request {
        return Networker.send(request: api.request, callback: handleResponse(result))
    }
    
    private static func handleFailure(_ error: Request.Error, callback: ResultHandler?) {
        print("""
            😭😭😭😭😭😭  Request Failed  😭😭😭😭😭😭 \nprivate 
            | Error | \(error.localizedDescription)\n
            ============== End ==============\n
        """)
        GCD.asyncInMainQueue {
            callback?(.failed(error: error))
        }
    }
    private static func handleSuccess(_ data: T, callback: ResultHandler?) {
        print("""
            😘😘😘😘😘😘  Request Success  😘😘😘😘😘😘\n
            | Data | \(data)\n
            ============== End ==============\n
        """)
        GCD.asyncInMainQueue {
            callback?(.success(data: data))
        }
    }
    
    private static func handleResponse(_ callback: ResultHandler?) -> Request.ResultingData
    {
        { result in
            switch result {
            case .success(let data):
                do {
                    let object = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
                    guard JSONSerialization.isValidJSONObject(object) else
                    {
                        handleFailure(.invalidJsonObject, callback: callback)
                        return
                    }
                    let jsonStr = String(data: data, encoding: .utf8)
                    guard let jsonObj = T.deserialize(from: jsonStr) else {
                        handleFailure(.json2ModelError, callback: callback)
                        return
                    }
                    handleSuccess(jsonObj, callback: callback)
                }
                catch {
                    handleFailure(.data2JsonError, callback: callback)
                }
                
            case .failed(let error):
                handleFailure(.responseError(error), callback: callback)
            case .loading(let request):
                GCD.asyncInMainQueue {
                    callback?(.loading(request: request))
                }
            case .finished(let request):
                GCD.asyncInMainQueue {
                    callback?(.finished(request: request))
                }
            }
        }
    }
}
